﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FarseerGames.FarseerPhysics.Dynamics.Joints;
using FarseerGames.FarseerPhysics.Mathematics;

namespace FreeGoo.GameEngine.Renderables.Links
{
    public abstract class Link : Renderable
    {
        private const float AdjustLengthTime = 1.0f; // seconds
        private float? _startTargetDistance;
        private int _startAdjustFrameNumber;
        private int _stopAdjustFrameNumber;
        public Link(ILinkable linkable1, ILinkable linkable2, ILinkable owningLinkable)
            : base()
        {
            Linkable1 = linkable1;
            Linkable2 = linkable2;
            OwningLinkable = owningLinkable;

            CreateJoint();

            Linkable1.AddLink(this);
            Linkable2.AddLink(this);

            WantedTargetDistance = null;
        }

        public Joint Joint { get; protected set; }
        public ILinkable Linkable1 { get; protected set; }
        public ILinkable Linkable2 { get; protected set; }
        public float? WantedTargetDistance { get; set; }
        public override Vector2 Position
        {
            get
            {
                return (Linkable1.Body.Position + Linkable2.Body.Position) / 2;
            }

            set
            {
            }
        }

        public abstract Vector2 LinkStart { get; }
        public abstract Vector2 LinkEnd { get; }

        public ILinkable OwningLinkable { get; protected set; }

        public abstract float TargetDistance { get; set; }

        public override float Rotation
        {
            get
            {
                return (float)Math.Atan2(
                    Linkable2.Body.Position.Y - Linkable1.Body.Position.Y,
                    Linkable2.Body.Position.X - Linkable1.Body.Position.X);
            }

            set
            {
            }
        }

        public override void Delete()
        {
            base.Delete();
            Engine.Links.Remove(this);
            Linkable1.RemoveLink(this);
            Linkable2.RemoveLink(this);
            Joint.Broke -= Engine.PinJoint_Broke;
            Engine.PhysicsSimulator.Remove(Joint);
        }

        public ILinkable OtherLinkable(ILinkable linkable)
        {
            return Linkable1 != linkable ? Linkable1 : Linkable2;
        }

        public override void UpdatePhysics()
        {
            base.UpdatePhysics();
            AdjustTargetDistance();
        }

        public override void AddToEngine(Engine engine)
        {
            base.AddToEngine(engine);
            engine.Links.Add(this);
            Joint.Broke += engine.PinJoint_Broke;
            _startAdjustFrameNumber = Engine.PhysicsFrameNumber;
            _stopAdjustFrameNumber = Engine.PhysicsFrameNumber + (int)Math.Round(AdjustLengthTime / Engine.TimeStep);
            Engine.PhysicsSimulator.Add(Joint);
        }

        public abstract float DistanceToPoint(Vector2 point);

        protected abstract void CreateJoint();

        private float EaseInEaseOut(float x)
        {
            if (x > 1)
            {
                return x;
            }

            if (x < 0)
            {
                return 0;
            }

            return 3 * x * x - 2 * x * x * x;
        }

        private void AdjustTargetDistance()
        {
            if (WantedTargetDistance != null && Engine.PhysicsFrameNumber < _stopAdjustFrameNumber)
            {
                if (_startTargetDistance == null)
                {
                    _startTargetDistance = TargetDistance;
                }

                float pos = (Engine.PhysicsFrameNumber - (float)_startAdjustFrameNumber) / (_stopAdjustFrameNumber - _startAdjustFrameNumber);
                float ease = EaseInEaseOut(pos);

                float _wantedDistance = (float)WantedTargetDistance * ease + (1 - ease) * (float)_startTargetDistance;

                TargetDistance = _wantedDistance;
            }
        }
    }
}
